home *** CD-ROM | disk | FTP | other *** search
- #
- # File: tcprelay.pl
- # Author: Kazumasa Utashiro
- # Modified: G. Paul Ziemba
- # From: tcprelay,v 1.2 1992/04/13 19:10:28 utashiro
- # Date: 93.01.25
- # SCCS: @(#)tcprelay.pl 1.7 3/2/93
- # Purpose: application-level tcp stream relay; handles ftp, too.
- #
- ;#
- ;# tcprelay: application level tcp bridge
- ;#
- ;# Copyright (c) 1990,1991,1992 Kazumasa Utashiro
- ;# Software Research Associates, Inc., Japan <utashiro@sra.co.jp>
- ;#
- ;# Version 1.0, Oct 29 1990
- ;# Version 1.1, Jan 21 1991
- ;; $rcsid = '$Id: tcprelay,v 1.2 1992/04/13 19:10:28 utashiro Exp $';#'
- ;#
- ;# Usage:
- ;# tcprelay [switches] servername clientname [service]
- ;# tcprelay [switches] -i
- ;#
- ;# Switches:
- ;# -fg: force foregound
- ;# -bg: force backgound [default]
- ;# -ftp: force ftp mode (automatically on when connecting to ftp port)
- ;#
- ;# Description:
- ;# This program relays tcp connection in application layer, which
- ;# is useful when connecting across the IP disjoint gateway.
- ;# Tcprelay connect to specified server and then makes local port
- ;# and listen for connection from the client. After tcprelay listen,
- ;# anybody can connect to that port, so client name is required
- ;# to avoid unexpected connect request. Local port number is not
- ;# explicitly defined, so you have to see message from tcprelay
- ;# and invoke internet command with that number on client machine.
- ;#
- ;# -------- ----|---- --------
- ;# |client|---|gateway|---|server|
- ;# -------- ----|---- --------
- ;#
- ;# If the session seems to be ftp, tcprelay fakes PORT command
- ;# in ftp interaction. It makes connection to port in CLIENT which
- ;# is specified in PORT command, and makes local socket to listen from
- ;# ftp SERVER and returns that local port number to SERVER instead of
- ;# the number sent from ftp CLIENT. Use -ftp option when you want to
- ;# connect to ftpd which doesn't have standard port number 21.
- ;#
- ;# Default service is ftp, because this program is made for doing
- ;# ftp originaly.
- ;#
- ;# Example:
- ;# 1) % tcprelay server client : on gateway
- ;# port=xxxx : remember port number in message
- ;# 2) % ftp gateway xxxx : on client
- ;#
- ;# require 'sys/socket.ph';
- unless (do 'sys/socket.ph') {
- eval 'sub SOCK_STREAM {1;} sub AF_INET {2;} sub PF_INET {2;}';
- }
-
- if ($> == 0) {
- #
- # Shouldn't run as root!
- #
- $) = -2;
- $( = -2;
- $< = -2;
- $> = -2;
- }
-
- #
- # Default path unless specified in Makefile
- #
- $ENV{'PATH'} = "/bin:/usr/bin:/etc:/usr/etc:/usr/ucb";
-
- while ($_ = $ARGV[0], /^-/) {
- shift;
- if (/-s$/) {$silent = 1; next;}
- # if (/-i$/) {$interactive = 1; next;}
- if (/-ftp$/) {$ctype = 'ftp'; next;}
- if (/-d(\d*)$/) {$debug = $1||1; next;}
- if (/-(fg|bg)$/) {$fg = $1 eq 'fg'; next;}
- &usage;
- }
- $progname = $0;
- $progname =~ s:.*/::g;
- $| = 1;
- if ($interactive) {
- print "Enter server name or address: "; chop($servername=<>);
- print "Enter client name or address: "; chop($clientname=<>);
- } else {
- if ($#ARGV < $[+1) {&usage;}
- ($servername, $clientname, $serverport, $localport) = @ARGV;
- }
- sub usage {
- print "900 $0: Usage\n";
- ($myname = $0) =~ s|.*/||;
- print "Usage: $myname server client, or $myname -i\n";
- print "$rcsid\n" if $rcsid =~ /:/;
- exit(1);
- }
-
- $sockaddr='S n a4 x8';
- ($name, $aliases, $TCP) = getprotobyname('tcp');
- $serverport='ftp' unless $serverport;
-
- chop($localname = `hostname`);
- ($name, $aliases, $type, $len, $localaddr) = gethostbyname($localname);
-
- ($serveraddr = &getaddr($servername)) || die "Unknown server $servername.\n";
- ($clientaddr = &getaddr($clientname)) || die "Unknown client $clientname.\n";
- ($name, $aliases, $serverport) =
- getservbyname($serverport, 'tcp') unless $serverport =~ /^\d+$/;
- if (!defined($ctype)) {
- if ($serverport == 21) {$ctype = 'ftp';}
- else {$ctype = 'something';}
- }
- $masterpid=$$;
- $SIG{'HUP'}=$SIG{'INT'}=$SIG{'QUIT'}=$SIG{'TERM'}='terminate';
- sub terminate {kill -15, $masterpid; exit(1);}
- $SIG{'ALRM'} = 'IGNORE';
-
- &relay($ctype, $serveraddr, $serverport);
-
- sub relay {
- local($type, $serveraddr, $serverport, $newport)=@_;
- local($toplevel) = ($$ eq $masterpid);
-
- #
- # server connection
- # TBD - probably should time out here
- #
- $this = pack($sockaddr, &AF_INET, 0, $localaddr);
- $that = pack($sockaddr, &AF_INET, $serverport, $serveraddr);
- socket(S1, &PF_INET, &SOCK_STREAM, $TCP) || die "socket: $!";
- # bind(S1, $this) || die "bind: $!";
- connect(S1, $that) || die "connect: $!";
- select(S1); $| = 1; select(stdout);
-
- &logit( ($toplevel? "900 C": "920 Slave c") .
- "onnected to server " . join('.', unpack('C4', $serveraddr)));
- &logit("900 Connection type is ftp\n") if ($type eq 'ftp');
-
- #
- # client connection
- #
- $this = pack($sockaddr, &AF_INET, $localport, "\0\0\0\0");
- $localport = 0;
- socket(A1, &PF_INET, &SOCK_STREAM, $TCP) || die "socket: $!\n";
- bind(A1, $this) || die "bind: $!\n";
- listen(A1, 1) || die "listen: $!\n";
- $newport = (unpack($sockaddr, getsockname(A1)))[1];
- if (!$toplevel && !fork) {
- close(S1); close(A1);
- return($newport);
- }
- close(S), close(C) unless $toplevel;
- open(S,"+>&S1"); close(S1);
- open(A,"+>&A1"); close(A1);
- printf "Please connect to port=%d\n", $newport if $toplevel;
-
- #
- # time out here in case client has gone away
- #
- $SIG{'ALRM'} = 'client_timeout';
- &alarm(180);
- ($addr = accept(C, A)) || die "accept: $!\n";
- &alarm(0);
- close(A);
-
- ($af, $peerport, $peeraddr) = unpack($sockaddr, $addr);
- if ($toplevel && $peeraddr ne $clientaddr) {
- printf ("910 Connection from %s is not allowed!\n",
- join('.', unpack('C4', $peeraddr)));
- exit(1);
- }
- if ($toplevel) {
- &logit(sprintf ("920 Connection from client %s\n",
- join('.', unpack('C4', $peeraddr))));
- }
-
- select(S); $| = 1; select(C); $| = 1; select(stdout);
- if ($child = fork) {
- if ($toplevel && !$fg && ($pid = fork)) {
- &logit("900 Remote -> Client (pid = $pid)\n");
- &logit("900 $$: exiting");
- exit(0);
- }
- &forward('data', S, C, $serveraddr);
- } else {
- &logit("900 Client -> Remote (pid = $$)");
- &forward($type, C, S, $serveraddr); # serveraddr needed here
- }
- &logit("900 $$: exiting\n");
- exit(0);
- }
-
- sub forward {
- local($type, $from, $to, $serveraddr) = @_;
-
- if ($type eq 'ftp') {
- local($myportaddr) = &best_if_addr($serveraddr);
-
- &logit("900 (ftp) i/f to remote: " .
- join('.', unpack('C4', $myportaddr)));
-
- while (<$from>) {
- #
- # Perhaps this match won't work sometimes if we get
- # non-line chunks, since it's not line-buffered.
- # Maybe this stream ought to be line buffered (?)
- #
- if (/^PORT ([\d,]+)/ && (@p = split(/,/, $1))) {
- &logit("920 R PORT->$_");
- $p = &relay('data', pack('C4', @p), $p[4]*256 + $p[5]);
- #
- # output in netascii (<stuff>\r\n)
- #
- $_ = sprintf("PORT %d,%d,%d,%d,%d,%d\r\n",
- unpack('C4', $myportaddr), $p/256, $p%256);
- &logit("920 S PORT->$_");
- }
- print $to $_;
- }
- } else {
- print $to $_ while(read($from, $_, 4096));
- }
- shutdown($from, 1); shutdown($to, 0);
- }
-
- sub getaddr {
- local($_) = @_;
- /^[0-9\.]+$/ ? pack("C4", split(/\./)) : (gethostbyname($_))[4];
- }
-
- #
- # find route to destination & return address of appropriate interface
- #
- sub best_if_addr { # remote addr
- local($serveraddr) = $_[0];
- local($server_dq) = sprintf("%d.%d.%d.%d", unpack('C4', $serveraddr));
- local($OldPath, $NsProg);
-
- ($ENV{'PATH'}, $NsProg, $OldPath) = &pathit($NETSTATPATH, "netstat");
-
- open(RT, "$NsProg -rn|") || die "$NsProg: $!";
- $ENV{'PATH'} = $OldPath;
-
- while (<RT>) {
- split(?\s+?, $_);
-
- if (/^default/) {
- $IfD = $_[5];
- }
- next if ($_[0] !~ /^([\d\.]+)$/);
- $Dest = $_[0];
-
- next if ($_[2] !~ /U/);
- if ($_[2] =~ /H/) {
- ($GH{$Dest}, $IfH{$Dest}) = @_[1,5];
- } else {
- ($GN{$Dest}, $IfN{$Dest}) = @_[1,5];
- }
- }
- close(RT);
-
- foreach (keys(%GH)) {
- if ($_ eq $server_dq) {
- &logit("900 Host route to $_: $GH{$_} via $IfH{$_}\n");
- return &ifaddr($IfH{$_});
- }
- }
- foreach (keys(%GN)) {
- $_a = $_;
-
- s/(\.0)*$//; # leave only net part
- s/(\W)/\\$1/g; # quote metacharacters
-
- if ($server_dq =~ /^$_/) {
- &logit("900 Net route to $_a: $GN{$_a} via $IfN{$_a}\n");
- return &ifaddr($IfN{$_a});
- }
- }
- if (defined($IfD)) {
- &logit("900 no Net or Host route, using default route: $IfD\n");
- return &ifaddr($IfD);
- }
- &logit(sprintf("900 &bia: No Host, Net, or Default route, using %s\n",
- join('.', unpack('C4', $localaddr))));
- return $localaddr;
- }
-
- sub ifaddr { # ifname
- local($ifname) = $_[0];
- local($ip);
- local($OldPath, $IfProg);
-
- ($ENV{'PATH'}, $IfProg, $OldPath) = &pathit($IFCONFIGPATH, "ifconfig");
-
- &logit("900 ifaddr: want IP-addr for: $ifname\n");
- open (IFCONFIG, "$IfProg $ifname|") || die "ifconfig: $!";
- $ENV{'PATH'} = $OldPath;
- while (<IFCONFIG>) {
- chop;
- if (/\s+inet\s+(\S+)\s+/) {
- $ip = $1;
- last;
- }
- }
- close(IFCONFIG);
- if (!defined($ip)) {
- local($dq) = join('.', unpack('C4', $localaddr));
-
- #
- # This is a band-aid
- #
- &logit("900 ifaddr: can't parse ifconfig output, returning $dq\n");
- return $localaddr;
- }
- return pack('C4', split(/\./, $ip));
- }
-
- #
- # It's gross to do a fork for every message, but this
- # should only be needed for those occasional debugging
- # sessions :-)
- #
- sub logit {
- local(@message) = split(/\n/, $_[0]);
- local($kidpid, $_);
-
- return if (!$debug);
-
- for (@message) {
- print STDERR "$_\n";
- $_ = "[$$] " . $_;
- $kidpid = fork;
- if (!$kidpid) {
- local($LogProg);
- ($ENV{'PATH'}, $LogProg, $kidpid) =
- &pathit($LOGGERPATH, "logger");
-
- exec $LogProg, "-t", $progname, "-p", "daemon.debug", $_;
- exit -1;
- } else {
- waitpid($kidpid, 0);
- }
- }
- }
-
- sub pathit { # Args: path it; Returns: NewPath, ProgName, OldPath
- local($_, $it) = @_[0,1];
- local(@retval);
-
- if (/./) {
- if (!/:/) {
- if (-d) {
- $_ .= "/$it";
- }
- @retval = ("", $_, $ENV{'PATH'});
- printf STDERR "[%s] [%s] [%s]\n", @retval[0..2];
- return @retval;
- }
- @retval = ($_, $it, $ENV{'PATH'});
- printf STDERR "[%s] [%s] [%s]\n", @retval[0..2];
- return @retval;
- }
- @retval = ($ENV{'PATH'}, $it, $ENV{'PATH'});
- printf STDERR "[%s] [%s] [%s]\n", @retval[0..2];
- return @retval;
- }
-
- sub client_timeout {
- &logit("900 client connection timed out. Exiting.");
- &terminate;
- }
-
- sub alarm {
- local($timeout) = $_[0];
-
- &logit("alarm: called with $timeout");
-
- #
- # If we forked, make sure we forget about alarm kid of parent
- #
- if ($CallerPid != $$) {
- &logit("alarm: called first time in pid $$");
- undef $AlarmPid;
- $CallerPid = $$;
- }
-
- #
- # If we have an alarm kid, kill it
- #
- if (defined($AlarmPid)) {
- &logit("alarm: killing ak $AlarmPid");
- kill 'KILL', $AlarmPid;
- }
-
- #
- # Don't have to do anything else if zero timeout argument
- #
- if (!$timeout) {
- return 0;
- }
-
- $AlarmPid = fork;
- if ($AlarmPid) {
- return 0;
- } elsif (!defined($AlarmPid)) {
- #
- # fork failed
- # (is this the correct test for fork failure?)
- #
- &logit("alarm: fork failed");
- return -1;
- }
-
- #
- # Child from here on
- #
- &logit("ak: sleeping for $timeout");
- sleep($timeout);
- &logit("ak: alarming $CallerPid");
- kill 'ALRM', $CallerPid;
- exit 0;
- }
-